﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using System.Windows;
using System.Diagnostics;
using SilverTile.Viewports;

namespace SilverTile.Sprites
{
    [DebuggerDisplay("Sprite ({Area})")]
    public class Sprite : Renderable, ISprite, IUpdateable
    {

        /// <summary>
        /// Instansiates a sprite with the given width and height
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public Sprite(int width, int height)
            : base(width, height)
        {
            _frame = new Frame(width, height);
        }

        /// <summary>
        /// Instansiate a sprite from the given image frame
        /// </summary>
        /// <param name="imageFrame"></param>
        public Sprite(IFrame imageFrame)
            : base(imageFrame)
        { }

        public Sprite(string uri)
            : base(new Frame(uri))
        { }

        public Sprite(Uri uri)
            : base(new Frame(uri))
        { }


        public int[] Pixels
        {
            get { return _frame.Pixels; }
        }

        public virtual int X { get; set; }
        public virtual int Y { get; set; }
        public virtual int Z { get; set; }
        public bool Blocking { get; set; }

        private bool _isVisible = true;
        public bool IsVisible { get { return _isVisible; } set { _isVisible = value; } }

        public virtual int OriginX { get { return _frame.OriginX; } }
        public virtual int OriginY { get { return _frame.OriginY; } }

        public Rect Area
        {
            get
            {
                Rect rect = _frame.Bounds;
                if (rect.EmptyRect())
                {
                    return new Rect(new Point(X - OriginX, Y - OriginY), new Size(Width, Height));
                }
                else
                {
                    return rect.Replace(X, Y);
                }
            }
        }

        public Rect BackgroundArea
        {
            get
            {
                return _frame.BackgroundBounds;
            }
        }

        public IViewport Viewport { get; set; } 

        public Collision CollidesWithDir(ISprite otherSprite)
        {
            return CollidesWithDir(otherSprite, X, Y, 0, false);
        }

        public Collision CollidesWithDir(ISprite otherSprite, int expandBy)
        {
            return CollidesWithDir(otherSprite, X, Y, expandBy, false);
        }

        public Collision CollidesWithBackground(ISprite otherSprite, int x, int y, int expandBy)
        {
            return CollidesWithDir(otherSprite, x, y, expandBy, true);
        }

        public Collision CollidesWithDir(ISprite otherSprite, int x, int y, int expandBy)
        {
            return CollidesWithDir(otherSprite, x, y, expandBy, false);
        }

        private Collision CollidesWithDir(ISprite otherSprite, int x, int y, int expandBy, bool useBackgroundBounds)
        {
    
            if (!CollidesWith(otherSprite, x, y, expandBy))
            {
                return Collision.None;
            }

            Rect thisRect =Area.Expand(expandBy).Replace(x,y); 
            if(useBackgroundBounds)
            {
                thisRect = BackgroundArea.Expand(expandBy).Shift(x, y);
            }
            Rect origRect = thisRect;
            Rect otherRect = otherSprite.Area.Expand(expandBy); 

            Collision collision = Collision.None;

            Point ul = new Point(origRect.Left, origRect.Top);
            Point ur = new Point(origRect.Right, origRect.Top);
            Point bl = new Point(origRect.Left, origRect.Bottom);
            Point br = new Point(origRect.Right, origRect.Bottom);

            if (otherRect.Contains(ul))
            {
                collision = collision.Set(Collision.Top).Set(Collision.Left);
            }

            if (otherRect.Contains(ur))
            {
                collision = collision.Set(Collision.Top).Set(Collision.Right);
            }

            if (otherRect.Contains(bl))
            {
                collision = collision.Set(Collision.Bottom).Set(Collision.Left);
            }

            if (otherRect.Contains(br))
            {
                collision = collision.Set(Collision.Bottom).Set(Collision.Right);
            }

            Point oul = new Point(otherRect.Left, otherRect.Top);
            Point our = new Point(otherRect.Right, otherRect.Top);
            Point obl = new Point(otherRect.Left, otherRect.Bottom);
            Point obr = new Point(otherRect.Right, otherRect.Bottom);

            if (!collision.IsSet(Collision.Top) && !origRect.Contains(oul) && !origRect.Contains(our) && origRect.Contains(obl) && origRect.Contains(obr))
            {
                collision = collision.Set(Collision.Top);
            }

            if (!collision.IsSet(Collision.Bottom) && origRect.Contains(oul) && origRect.Contains(our) && !origRect.Contains(obl) && !origRect.Contains(obr))
            {
                collision = collision.Set(Collision.Bottom);
            }

            if (!collision.IsSet(Collision.Right) && origRect.Contains(oul) && !origRect.Contains(our) && origRect.Contains(obl) && !origRect.Contains(obr))
            {
                collision = collision.Set(Collision.Right);
            }

            if (!collision.IsSet(Collision.Left) && !origRect.Contains(oul) && origRect.Contains(our) && !origRect.Contains(obl) && origRect.Contains(obr))
            {
                collision = collision.Set(Collision.Left);
            }

            if (collision == Collision.None)
            {
                if (origRect.Contains(oul) && origRect.Contains(our) && origRect.Contains(obl) && origRect.Contains(obr))
                {
                    collision = Collision.All;
                }
            }

            collision = collision.Clear(Collision.None);

            return collision;

        }

        public bool CollidesWith(ISprite otherSprite)
        {
            return CollidesWith(otherSprite, X, Y, 0);
        }

        public bool CollidesWith(ISprite otherSprite, int x, int y)
        {
            return CollidesWith(otherSprite, x, y, 0);
        }

        public bool CollidesWith(ISprite otherSprite, int x, int y, int expandBy)
        {

            Rect thisRect = Area.Expand(expandBy).Replace(x,y); // new Rect(new Point(x - expandBy, y - expandBy), new Size(Width + expandBy * 2, Height + expandBy * 2));
            Rect otherRect = otherSprite.Area.Expand(expandBy); // new Rect(new Point(otherSprite.X - expandBy, otherSprite.Y - expandBy), new Size(otherSprite.Width + expandBy * 2, otherSprite.Height + expandBy * 2));

            thisRect.Intersect(otherRect);

            return !thisRect.IsEmpty;
        }
    }
}
